#include "StdAfx.h"
#include "GeometricTools.h"
#include <iostream>

const float GeometricTools::FloatSmallValue = 0.0001f;


GeometricTools::GeometricTools(void)
{
}


GeometricTools::~GeometricTools(void)
{
}

bool GeometricTools::Intersect(Scene *pScene, Line* pLine)
{
    IntersectInfo info;
    return pScene->Intersect(pLine, &info);

    return false;
	//throw std::exception("The method or operation \"GeometricTools::Intersect\" is not implemented.");
}

bool GeometricTools::Intersect(Scene *scene, Line& line,
	Triangle* triangle, Vector3f* intersectionPoint, Vector3f* surfaceNormal, Material* material)
{
	throw std::exception("The method or operation \"GeometricTools::Intersect\" is not implemented.");
}

Vector2 GeometricTools::CalculateTextureCoordinate(Triangle& triangle, Vector3f& intersectionPoint)
{
	// TODO
	return Vector2(0.0f, 0.0f);
}

void GeometricTools::Refract(Line& incidentRay, 
	Triangle& triangle, Vector3f& intersectionPoint, Vector3f& surfaceNormal, 
	Material& currentMaterial, Material& material, Line* refraction)
{
	// see http://www.opengl.org/sdk/docs/manglsl/xhtml/refract.xml

	Vector3f incident = incidentRay.GetDirection();
	Vector3f normal = surfaceNormal;

	float iDn = Dot(normal, incident);
	float eta = material.GetRefractiveIndex();
	if (iDn > 0)
	{
		iDn = -iDn;
		normal = -normal;
		eta = 1 / material.GetRefractiveIndex();
		// FIXME temp. if alpha is used, incidentRay can go from the back face of a surface.
		//std::cout << "in GeometricTools::Refract, surfaceNormal is not in the same half plane as the incidentRay." << std::endl;
	}

	Vector3f refractionDirection;
	float k = 1.0 - eta * eta * (1.0 - iDn * iDn);
	if (k >= 0.0)
	{
		refractionDirection = incident * eta - surfaceNormal * (eta * iDn + sqrt(k));
	}
	else
	{
		std::cout << "in GeometricTools::Refract, k < 0" << std::endl;
	}

	// Adding -(normal * FloatSmallValue) to start point of refractionRay is to avoid it started above the refracting surface.
	*refraction = Line(intersectionPoint - normal * FloatSmallValue, refractionDirection, NULL);
}

void GeometricTools::Reflect(Line& incidentRay,
	Triangle& triangle, Vector3f& intersectionPoint, Vector3f& surfaceNormal, Line* reflectionRay)  
{
	// see http://www.opengl.org/sdk/docs/manglsl/xhtml/reflect.xml

	Vector3f incident = -incidentRay.GetDirection();
	Vector3f normal = surfaceNormal;
	float iDn = Dot(normal, incident);
	if (iDn < -0)
	{
		normal = -normal;

		// FIXME temp. if alpha is used, incidentRay can go from the back face of a surface.
		//std::cout << "in GeometricTools::Reflect, surfaceNormal is not in the same half plane as the incidentRay." << std::endl;
	}

	Vector3f reflectionDirection = normal * iDn * 2.0 - incident;

	// Adding normal * FloatSmallValue to start point of reflectionRay is to avoid it started beneath the reflecting surface.
	*reflectionRay = Line(intersectionPoint + normal * FloatSmallValue, reflectionDirection, NULL);

}


bool GeometricTools::LineIntersectXYPlane(float* fLineDirection, float* fLinePoint, float* fPointIntersect)
{
    // Zd == 0, parallel with the plane
    if (0 == fLineDirection[2])
    {
        return false;
    }


    // for the line: L(t) = s + td
    float Xd = fLineDirection[0], Yd = fLineDirection[1], Zd = fLineDirection[2];
    float Xs = fLinePoint[0],     Ys = fLinePoint[1],     Zs = fLinePoint[2];

    float t = -1 * Zs / Zd;

    fPointIntersect[0] = Xs + t * Xd;
    fPointIntersect[1] = Ys + t * Yd;
    fPointIntersect[2] = Zs + t * Zd;

    return true;

}

bool GeometricTools::LineIntersectPlane(float* fLineDirection, float* fLinePoint, 
                                      float* fPlaneNormal, float* fPlanePoint,
                                      float* fPointIntersect)
{
    float t = 0;

    // for the plane: Xn(x - X0) + Yn(y - Y0) + Zn(z - Z0) = 0
    float Xn = fPlaneNormal[0], Yn = fPlaneNormal[1], Zn = fPlaneNormal[2];
    float X0 = fPlanePoint[0],  Y0 = fPlanePoint[1],  Z0 = fPlanePoint[2];

    // for the line: L(t) = s + td
    float Xd = fLineDirection[0], Yd = fLineDirection[1], Zd = fLineDirection[2];
    float Xs = fLinePoint[0],     Ys = fLinePoint[1],     Zs = fLinePoint[2];

    t = -1 * (Xn*(Xs-X0) + Yn*(Ys-Y0) + Zn*(Zs-Z0)) / (Xn*Xd + Yn*Yd + Zn*Zd);

    if (t > 0)
    {   
        fPointIntersect[0] = Xs + t * Xd;
        fPointIntersect[1] = Ys + t * Yd;
        fPointIntersect[2] = Zs + t * Zd;

        return true;
    }

    return false;
}


bool GeometricTools::LineIntersectSphere(float* fLineDirection, float* fLinePoint, 
                                       float* fShpereCenter, float fSpereRadius,
                                       float* fPointIntersect)
{    
    // for the line: L(t) = s + td
    float Xd = fLineDirection[0], Yd = fLineDirection[1], Zd = fLineDirection[2];
    float Xs = fLinePoint[0],     Ys = fLinePoint[1],     Zs = fLinePoint[2];


    // vector S - C
    float sc[] = {Xs-fShpereCenter[0], Ys-fShpereCenter[1], Zs-fShpereCenter[2]};

    // A = |d|*|d|, B = (S - C)*d, C = |S - C|*|S - C| - R * R;
    float A = fLineDirection[0] * fLineDirection[0] 
    + fLineDirection[1] * fLineDirection[1] 
    + fLineDirection[2] * fLineDirection[2];
    float B = sc[0]*fLineDirection[0] + sc[1]*fLineDirection[1] + sc[2]*fLineDirection[2];
    float C = sc[0]*sc[0] + sc[1]*sc[1] + sc[2]*sc[2]  - fSpereRadius*fSpereRadius;

    float delta = B*B - A*C;
    float t[2];
    if (delta > 0)
    {
        t[0] = (-B + sqrt(delta)) / A;
        t[1] = (-B - sqrt(delta)) / A;

        // Get the nearest intersect point. And it must be positive.
        if (t[0] > t[1])
        {
            swap(t[0], t[1]);
        }

        for (int i = 0; i < 2; ++i)
        {
            // I guess it's the problem whick make the line intersect a sphere.
            // when t is very small (such as 1.0e-6), it should be know as 0.
            if (t[i] > FloatSmallValue)
            {    
                fPointIntersect[0] = Xs + t[i] * Xd;
                fPointIntersect[1] = Ys + t[i] * Yd;
                fPointIntersect[2] = Zs + t[i] * Zd;
                return true;
            }
        }
    }

    return false;
}

bool GeometricTools::LineIntersectSphere(
                         Sphere *pSphere, Line* line, Vector3f* intersectionPoint, Vector3f* surfaceNormal, 
                         Material* material, Line* reflectionRay)
{
    // for the line: L(t) = s + td
    float Xd = line->GetDirection().x, Yd = line->GetDirection().y, Zd = line->GetDirection().z;
    float Xs = line->GetPos().x,       Ys = line->GetPos().y,       Zs = line->GetPos().z;

    Vector3f vSphereCenter = pSphere->GetCenter();
    float fSphereRadius = pSphere->GetRadius();

    // vector S - C
    float sc[] = {Xs-vSphereCenter.x, Ys-vSphereCenter.y, Zs-vSphereCenter.z};

    // A = |d|*|d|, B = (S - C)*d, C = |S - C|*|S - C| - R * R;
    float A = Xd * Xd + Yd * Yd + Zd * Zd;
    float B = sc[0]*Xd + sc[1]*Yd + sc[2]*Zd;
    float C = sc[0]*sc[0] + sc[1]*sc[1] + sc[2]*sc[2]  - fSphereRadius*fSphereRadius;

    float delta = B*B - A*C;
    float t[2];
    if (delta > 0)
    {
        t[0] = (-B + sqrt(delta)) / A;
        t[1] = (-B - sqrt(delta)) / A;

        // Get the nearest intersect point. And it must be positive.
        if (t[0] > t[1])
        {
            swap(t[0], t[1]);
        }

        for (int i = 0; i < 2; ++i)
        {
            // Fixed a problem.
            // I guess it's the problem whick make the line intersect a sphere.
            // when t is very small (such as 1.0e-6), it should be know as 0.
            // Remember, never use int to compare with float!
            if (t[i] > 0.001)
            {    
                // set the intersect point
                intersectionPoint->x = Xs + t[i] * Xd;
                intersectionPoint->y = Ys + t[i] * Yd;
                intersectionPoint->z = Zs + t[i] * Zd;

                // set the normal
                //surfaceNormal = Normalize(intersectionPoint - vSphereCenter);
                //*material = *(pSphere->GetMaterial());

                return true;
            }
        }
    }
/*
*/
    return false;
}
